package org.hawk.gwt.ppc.parser.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.hawk.gwt.ppc.parser.CompilationUnitDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.DescriptorVisitor;
import org.hawk.gwt.ppc.parser.ImportsDescriptor;
import org.hawk.gwt.ppc.parser.JavaKeywords;
import org.hawk.gwt.ppc.parser.PreprocessorParseException;
import org.hawk.gwt.ppc.parser.Token;
import org.hawk.gwt.ppc.utils.PreprocessorException;

/**
 * Parses imports declarations.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class ImportsDescriptorParser implements
		DescriptorParser {

	/**
	 * @return new empty imports descriptor (corresponds empty imports section)
	 */
	static ImportsDescriptor createEmptyImportsDescriptor() {
		return new ImportsDescriptorImpl(null);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.parser.impl.DescriptorParser#accepts(org.hawk.gwt.ppc.parser.Token)
	 */
	public boolean accepts(Token token) {
		return token.equalsTo(JavaKeywords.IMPORT);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.parser.impl.DescriptorParser#parse(org.hawk.gwt.ppc.parser.impl.TokenBasedJavaParser, org.hawk.gwt.ppc.parser.impl.Tokenizer)
	 */
	public Descriptor parse(TokenBasedJavaParser parser, TokenSource tokenizer, Object...params)
			throws PreprocessorException {
		ImportsDescriptorImpl result = new ImportsDescriptorImpl(tokenizer.current());
		while(accepts(tokenizer.current())) {
			readImport(result, parser, tokenizer);
		}
		
		return result;
	}
	
	private void readImport(ImportsDescriptorImpl result,
			TokenBasedJavaParser parser, TokenSource tokenizer) throws PreprocessorException {
		
		StringBuilder importDeclaration = new StringBuilder();
		
		if (!tokenizer.hasNext()) {
			throw new PreprocessorParseException("Unexpected end of file", tokenizer.current());
		}
		
		boolean staticFlag = false;
		Token last = null;
		if (tokenizer.next().equalsTo(JavaKeywords.STATIC)) {
			staticFlag = true;
		} else {
			last = tokenizer.current();
			last.writeTo(importDeclaration);
		}
		
		
		boolean dot = !staticFlag;
		
		Token current;
		while (tokenizer.hasNext() && !(current = tokenizer.next()).equalsTo(';')) {
			
			if ((dot && !current.equalsTo('.')) ||
				(!dot && !current.isWord() && !current.equalsTo('*'))) {
				throw new PreprocessorParseException("Wrong token", tokenizer.current());
			}
			dot = !dot;
			current.writeTo(importDeclaration);
			last = current;
		}
		
		if (!tokenizer.current().equalsTo(';') || !dot) {
			throw new PreprocessorParseException(tokenizer.current());
		}
		
		if (last.equalsTo('*')) {
			importDeclaration.deleteCharAt(importDeclaration.length() - 1);
			result.addWildcardImport(importDeclaration.toString(), staticFlag);
		} else {
			result.addImport(last.toString(), importDeclaration.toString(), staticFlag);
		}
		result.setLastToken(tokenizer.current());
		
		while (tokenizer.hasNext() && tokenizer.next().isComment()) ;
		
		if (tokenizer.current().isComment()) {
			tokenizer.eof();
		}
	}
	
	/**
	 * 
	 * @author alex.bereznevatiy@gmail.com
	 *
	 */
	private static class ImportsDescriptorImpl extends AbstractDescriptor implements ImportsDescriptor {
		
		ImportsDescriptorImpl(Token first) {
			super(first, null);
		}

		private List<String> wildcards = new LinkedList<String>();
		private List<String> wildcardsStatic = new LinkedList<String>();
		
		private Map<String, String> exact = new HashMap<String, String>();
		private Map<String, String> exactStatic = new HashMap<String, String>();

		private void addWildcardImport(String fullName, boolean staticFlag) {
			if(staticFlag) {
				wildcardsStatic.add(fullName);
			} else {
				wildcards.add(fullName);
			}
		}

		private void addImport(String name, String fullName, boolean staticFlag) {
			if(staticFlag) {
				exactStatic.put(name, fullName);
			} else {
				exact.put(name, fullName);
			}
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.ImportsDescriptor#resolveClass(java.lang.String)
		 */
		public List<String> resolveClass(String name) {
			String fullName = exact.get(name);
			if (fullName != null) {
				return Collections.singletonList(fullName);
			}
			
			List<String> result = new ArrayList<String>();
			
			CompilationUnitDescriptor cu = (CompilationUnitDescriptor)getParent();
			
			String pkgName = cu.getPackage().getPackageName();
			if (pkgName.length() > 0) {
				result.add(pkgName + '.' + name);
			}
			
			for (String pkg : wildcards) {
				result.add(pkg + name);
			}
			result.add("java.lang." + name);
			result.add(name);
			
			return Collections.unmodifiableList(result);
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.ImportsDescriptor#resolveMethod(java.lang.String)
		 */
		public List<String> resolveMethod(String name) {
			String fullName = exactStatic.get(name);
			if (fullName != null) {
				return Collections.singletonList(fullName);
			}
			
			List<String> result = new ArrayList<String>();
			
			for (String pkg : wildcardsStatic) {
				result.add(pkg + name);
			}
			
			return Collections.unmodifiableList(result);
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.Descriptor#visit(org.hawk.gwt.ppc.parser.DescriptorVisitor)
		 */
		public void visit(DescriptorVisitor visitor) throws PreprocessorException {
			visitor.visit(this);
		}
	}
}
